<?php

abstract class Model extends Base implements IValidator {

  /**
   * The validator object used to check each field of the object.
   * @var Validator The inherited class object to validate the current object's fields.
   */
  protected $validator = NULL;

  /**
   * The collection allows user to store many typed objects into and can iterate on it with a loop.
   * @var array The object collection.
   * @link http://be.php.net/manual/fr/function.array.php
   */
  public $Collection = array();

  /** @var ArrayList The $Items collection allows user to store many typed objects and can iterate on it. */
  public $Items = null;

  public function __construct() {
    $this->Items = new ArrayList();
  }

  /**
   * Build the object with a row obtained by a call to SQL::FetchAssoc() or an array.<br />
   * You can also redefine this function to parse the row in another way.
   * @param array $array The row usually obtained by a call to MsSQL::fetchAssoc().
   * @link http://be.php.net/manual/en/function.ucfirst.php
   * @link http://be.php.net/manual/en/function.is-callable.php
   */
  public function buildFromRow($array) {
    foreach ($array as $key => $value) {
      if (is_callable(array($this, "set" . ucfirst($key))))
        $this->{"set" . ucfirst($key)}($value);
    }
  }

  /**
   * Gets the number of items in the collection.
   * @return int The number of items in the collection
   * @deprecated Use the method provided by ArrayList on $Items.
   */
  public final function getLength() {
    return count($this->Collection);
  }

  /**
   * Add a model to the public collection.
   * @param Model $Item The object to push in the collection.
   * @return int Returns the new number of elements in the array.
   * @link http://us3.php.net/manual/en/function.array-push.php
   * @link http://fr2.php.net/manual/en/function.get-class.php
   * @deprecated Use the method provided by ArrayList on $Items.
   */
  public final function push($item) {
    if (is_a($item, get_class($this))) {
      return array_push($this->Collection, $item);
    } else {
      throw new IllegalArgumentException("\$item parameter should be an associated model object.");
    }
  }

  /**
   * Removes and returns the last object of the collection.
   * @return Base The last object of the collection.
   * @link http://us3.php.net/manual/en/function.array-pop.php
   * @deprecated Use the method provided by ArrayList on $Items.
   */
  public final function pop() {
    return array_pop($this->Collection);
  }

  /**
   * Shifts an element off the beginning of the collection.
   * @return Base The first object of the collection.
   * @link http://us3.php.net/manual/en/function.array-shift.php
   * @deprecated Use the method provided by ArrayList on $Items.
   */
  public final function shift() {
    return array_shift($this->Collection);
  }

  /**
   * Resets the collection.
   * @link http://be.php.net/manual/fr/function.array.php
   * @deprecated Use the method provided by ArrayList on $Items.
   */
  public final function emptyCollection() {
    $this->Collection = array();
  }

  /**
   * Resets the collection.
   * @link http://be.php.net/manual/fr/function.array.php
   * @deprecated Use the method provided by ArrayList on $Items.
   */
  public final function clear() {
    $this->Collection = array();
  }

  /**
   * Prepends one to the beginning of an array.
   * @param Model $Item The object to unshift of the collection.
   * @return The new number of elements in the colelction.
   * @link http://us3.php.net/manual/en/function.array-unshift.php
   * @deprecated Use the method provided by ArrayList on $Items.
   */
  public final function unshift($item) {
    if (is_a($item, get_class($this)))
      return array_unshift($this->Collection, $item);
    else
      throw new IllegalArgumentException("\$item parameter should be an associated object model.");
  }

  /**
   * Serializes and registers the object in session.
   *
   * @param boolean $session_register Set to True to register the object in session.<br />
   * If $session_register is set to FALSE, the serialized string object is returned.
   * @return string The serialized string object.
   * @link http://us3.php.net/manual/en/function.session-register.php
   * @link http://us3.php.net/manual/en/function.get-class.php
   * @link http://us3.php.net/manual/en/function.urlencode.php
   * @link http://us3.php.net/manual/en/function.serialize.php
   */
  public function serialize($session_register = true) {
    if ($session_register) {
      session_register(get_class($this));
      $_SESSION[get_class($this)] = urlencode(serialize($this));
    } else {
      return urlencode(serialize($this));
    }
  }

  /**
   * Unserialize and re-build a serialized object.
   * @return Base If string is not empty, rebuild the $string object, otherwise, try to rebuild a serialized object present in the session.
   * @link http://us3.php.net/manual/en/function.empty.php
   * @link http://us3.php.net/manual/en/function.get-class.php
   * @link http://us3.php.net/manual/en/function.unserialize.php
   * @link http://us3.php.net/manual/en/function.urldecode.php
   */
  public function unserialize($string = "") {
    if (empty($string)) {
      session_register(get_class($this));

      if (!empty($_SESSION[get_class($this)])) {
        $Object = unserialize(urldecode($_SESSION[get_class($this)]));

        $setters = get_class_methods(get_class($this));

        $counter = count($setters);

        for ($i = 0; $i < $counter; ++$i)
          if (substr($setters[$i], 0, 3) == "set")
            if (is_callable(array($Object, "g" . substr($setters[$i], 1))))
              $this->{$setters[$i]}($Object->{"g" . substr($setters[$i], 1)}());

        foreach ($Object->Collection as $Model) {
          $this->Push($Model);
        }
      }
    }
    else
      return unserialize(urldecode($string));
  }

  /**
   * Destroys the session object.
   * @link http://www.php.net/manual/en/function.unset.php
   */
  public function unsetSession() {
    unset($_SESSION[get_class($this)]);
  }

  /**
   * Serialize the current object and save the obtained string to a file.
   * @param string $filename The filename you want to use.<br />
   * The extension is automatically added with ".sob" (for Serialized OBject).
   * @link http://be.php.net/manual/en/function.is-string.php
   * @link http://be.php.net/manual/en/function.fopen.php
   * @link http://be.php.net/manual/en/function.fwrite.php
   * @link http://be.php.net/manual/en/function.fclose.php
   */
  public function saveToFile(String $filename) {
    if ($fp = fopen($filename->toString() . ".sob", "w")) {
      fwrite($fp, $this->Serialize(false));
      fclose($fp);
    }
  }

  /**
   * Rebuild the current object from a serialized one into a file.
   * @param string $filename The filename where the serialized object is located.<br />
   * Note that the extension is automatically added with ".sob" (for Serialized OBject).
   * @link http://be.php.net/manual/en/function.is-string.php
   * @link http://be.php.net/manual/en/function.fopen.php
   * @link http://be.php.net/manual/en/function.unserialize.php
   * @link http://be.php.net/manual/en/function.urldecode.php
   * @link http://be.php.net/manual/en/function.fread.php
   * @link http://be.php.net/manual/en/function.filesize.php
   * @link http://be.php.net/manual/en/function.fclose.php
   * @link http://be.php.net/manual/en/function.get-class-methods.php
   * @link http://be.php.net/manual/en/function.get-class.php
   * @link http://be.php.net/manual/en/function.count.php
   * @link http://be.php.net/manual/en/function.substr.php
   * @link http://be.php.net/manual/en/function.is-callable.php
   *
   */
  public function loadFromFile(String $filename) {
    if (file_exists($filename->toString() . ".sob")) {
      if ($fp = fopen($filename->toString() . ".sob", "r")) {
        $Object = unserialize(urldecode(fread($fp, filesize($filename->toString() . ".sob"))));

        fclose($fp);

        $setters = get_class_methods(get_class($this));

        $counter = count($setters);

        for ($i = 0; $i < $counter; ++$i)
          if (substr($setters[$i], 0, 3) == "set")
            if (is_callable(array($Object, "g" . substr($setters[$i], 1))))
              $this->{$setters[$i]}($Object->{"g" . substr($setters[$i], 1)}());

        foreach ($Object->Collection as $Model)
          $this->Push($Model);
      }
    } else {
      throw new FileNotFoundException("The file '" . $filename->toString() . "' was not found.");
    }
  }

  /**
   * Store the validator in private for further validation.
   * @param Validator $Validator The validator for the current object.
   * @link http://be.php.net/manual/en/function.method-exists.php">method_exists()</a>
   */
  public function setValidator($validator) {
    if (method_exists(get_class($validator), "validate")) {
      $this->validator = $validator;
    } else {
      throw new IllegalArgumentException("\$validator parameter should be an associated validator object.");
    }
  }

  /**
   * Call the validate method on a stored Validator.
   * @return boolean The result of the Validator::Validate method.
   * @link http://be.php.net/manual/en/function.empty.php
   */
  public function validate() {
    if (is_null($this->validator)) {
      throw new NullPointerException("Can not validate object without an overided method Validate()");
    } else {
      return $this->validator->validate();
    }
  }

}

?>
